// it's a shame!!!
package gr.uop.cst.context.base;

import java.util.Stack;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import static gr.uop.cst.context.base.ContextValueStatus.*;

/**
 * A <code>ContextObject</code> is a wrapper for a value that originates from
 * actual context of the application, as provided by a {@link ContextProvider}.
 * 
 * The primary purpose of a <code>ContextObject</code> is two-fold:
 *  <ul>
 *      <li>to provide its (probably time-varying) value, and</li>
 *      <li>to allow for a programmable value listener.</li>
 *  </ul>
 * It also provides accessor (getter) methods for its current value, and 
 * meta-data, such as its last modification time, its confidence (probability of 
 * its value&apos;s correctness), refresh period, validity period etc.
 * 
 * For more information about how to use <code>ContextObject</code>, along with
 * the rest core classes, see {@link gr.uop.cst.context.base}.

 * @author Ioannis Bourlakos &lt;jbourlakos@gmail.com&gt;
 * @version 0.1
 * @param <T> is the type of the values this object provides.
 * @see #addContextValueListener 
 * @see #newInstance 
 * @see #removeContextValueListener 
 * @see ContextValueEvent
 * @see gr.uop.cst.context.mechanics
 * @since 0.1
 * 
 */
public class ContextObject<T> implements ContextEvaluatable<T>, Activatable {
    private static final Stack<ContextObject> INSTANCES = new Stack<ContextObject>();

    /**
     * TODO
     */
    private transient volatile ContextValueStatus status;
    /**
     * The current value this object holds.
     * 
     * It is provided by the current <code>ContextProvider</code>. The value has
     * been fetched by the current <code>ContextSource</code>.
     * 
     * @see ContextProvider
     * @see ContextSource
     */
    private transient volatile T currentValue;
    /**
     * The last modification timestamp. 
     * 
     * It is provided by the current <code>ContextProvider</code>, which 
     * internally uses {@link System#currentTimeMillis() System}.
     * 
     * @see ContextProvider
     * @see System#currentTimeMillis()
     */
    private transient volatile long currentTimestamp;
    /**
     * The probability of correctness (confidence) for the current value.
     * 
     * It is provided by the current <code>ContextProvider</code>. The 
     * calculation of confidence concerns the implementation of 
     * <code>ContextSource</code> used.
     * 
     * @see ContextProvider
     * @see ContextSource
     */
    private transient volatile float currentConfidence;
    /**
     * TODO
     */
    private final float minConfidence;
    /**
     * TODO
     */
    private final int minRefreshPeriod;
    /**
     * TODO
     */
    private final int maxRefreshPeriod;
    /**
     * TODO
     */
    private final int minValidityPeriod;
    /**
     * TODO
     */
    private final int maxValidityPeriod;
    /**
     * <b>Attention:</b> currently unsupported. TODO: define and support metrics
     * on context value with respective transformations; for instance, Celsius
     * to Fahreneit scale, for temperature values.
     */
    private final Object metric;
    /**
     * TODO
     */
    private transient volatile ContextSource currentSource;
    /**
     * TODO
     */
    private final ContextProvider<T> provider;
    /**
     * TODO
     */
    private transient ContextObjectWorker<T> worker;
    /**
     * TODO
     */
    private final Collection<ContextValueListener<T>> listeners;
    /**
     * TODO
     */
    private long initializationTimestamp;

    /**
     * Factory construction
     * @param provider 
     * @param minConfidence 
     * @param maxRefreshPeriod
     * @param minValidityPeriod
     * @param minRefreshPeriod
     * @param maxValidityPeriod  
     */
    protected ContextObject(ContextProvider<T> provider, float minConfidence, int maxRefreshPeriod, int minRefreshPeriod, int maxValidityPeriod, int minValidityPeriod) {
        this.status = ContextValueStatus.UNAVAILABLE;
        this.provider = provider;
        this.minConfidence = minConfidence;
        this.minRefreshPeriod = minRefreshPeriod;
        this.maxRefreshPeriod = maxRefreshPeriod;
        this.minValidityPeriod = minValidityPeriod;
        this.maxValidityPeriod = maxValidityPeriod;
        this.worker = new ContextObjectWorker(this);
        this.initializationTimestamp = System.currentTimeMillis();
        this.listeners = new ArrayList<ContextValueListener<T>>(1);
        this.metric = null; // TODO
        INSTANCES.push(this);
    }

    /**
     * 
     * @param <T>
     * @param provider
     * @param minConfidence
     * @param maxRefreshPeriod
     * @param minRefreshPeriod
     * @param maxValidityPeriod
     * @param minValidityPeriod
     */
    protected static <T> void validateConstructorArguments(ContextProvider<T> provider, float minConfidence, int maxRefreshPeriod, int minRefreshPeriod, int maxValidityPeriod, int minValidityPeriod) {
        if (provider == null) {
            throw new IllegalArgumentException("ContextProvider can't be null.");
        }
        if ((minConfidence < 0.0) || (minConfidence > 1.0)) {
            throw new IllegalArgumentException("Value " + minConfidence + " is not valid for minimum confidence. Must be between 0.0 and 1.0 inclusive.");
        }
        if (minRefreshPeriod <= 0) {
            throw new IllegalArgumentException("Value " + minRefreshPeriod + "is not valid for minimum refresh period. Must be greater or equal to zero.");
        }
        if (maxRefreshPeriod < minRefreshPeriod) {
            throw new IllegalArgumentException("Value " + maxRefreshPeriod + "is not valid for maximum refresh period. Must be greater or equal to minimum refresh period.");
        }
        if (minValidityPeriod <= 0) {
            throw new IllegalArgumentException("Value " + minValidityPeriod + "is not valid for minimum validity period. Must be greater or equal to zero.");
        }
        if (maxValidityPeriod < minValidityPeriod) {
            throw new IllegalArgumentException("Value " + maxValidityPeriod + "is not valid for minimum validity period. Must be greater or equal to minimum validity period.");
        }
        if (minRefreshPeriod > minValidityPeriod) {
            throw new IllegalArgumentException("Minimum validity period cannot be less than minimum refresh period.");
        }
    }

    /**
     * 
     * @param <T>
     * @param provider
     * @param minConfidence 
     * @param maxRefreshPeriod 
     * @param minRefreshPeriod 
     * @param maxValidityPeriod 
     * @param minValidityPeriod 
     * @return 
     */
    public static <T> ContextObject<T> newInstance(ContextProvider<T> provider, float minConfidence, int maxRefreshPeriod, int minRefreshPeriod, int maxValidityPeriod, int minValidityPeriod) {
        validateConstructorArguments(provider, minConfidence, maxRefreshPeriod, minRefreshPeriod, maxValidityPeriod, minValidityPeriod);
        ContextObject<T> newObject =
                new ContextObject(provider, minConfidence, maxRefreshPeriod,
                minRefreshPeriod, maxValidityPeriod, minValidityPeriod);
        newObject.initiate();
        return newObject;
    }

    /**
     * 
     * @param <T>
     * @param provider
     * @param fixedRefreshedPeriod
     * @param fixedValidityPeriod
     * @return
     */
    public static <T> ContextObject<T> newInstance(ContextProvider<T> provider, int fixedRefreshedPeriod, int fixedValidityPeriod) {
        return newInstance(provider, 0.0f, fixedRefreshedPeriod, fixedRefreshedPeriod, fixedValidityPeriod, fixedValidityPeriod);
    }

    /**
     * 
     */
    protected void initiate() {
        worker.activate();
    }

    @Override
    protected void finalize() {
        try {
            this.worker.deactivate();
            super.finalize();
        } catch (Throwable ex) {
            // ignore, no problem
        }
    }

    /*
     * JavaBean behaviour - publicly immutable object *
     */
    
    /**
     * 
     * @return the currently assigned context value to this object.
     */
    @Override
    public synchronized final T getCurrentValue() {
        return this.currentValue;
    }

    final synchronized void setCurrentValue(T newValue) {
        this.currentValue = newValue;
    }

    /**
     * 
     * @return
     */
    public final ContextProvider<T> getContextProvider() {
        return this.provider;
    }

    synchronized final void setCurrentTimestamp(long time) {
        this.currentTimestamp = time;
    }

    /**
     * 
     * @return
     */
    public synchronized final long getCurrentTimestamp() {
        return this.currentTimestamp;
    }

    /**
     * 
     * @return
     */
    public long getMaxRefreshPeriod() {
        return this.maxRefreshPeriod;
    }

    /**
     * 
     * @return
     */
    public long getMinRefreshPeriod() {
        return this.maxRefreshPeriod;
    }

    @Override
    public synchronized ContextValueStatus getCurrentValueStatus() {
        return this.status;
    }

    /**
     * 
     * @return
     */
    public final ContextProvider<T> getProvider() {
        return provider;
    }

    synchronized final void setCurrentValueStatus(
            ContextValueStatus newStatus) {
        this.status = newStatus;
    }

    /**
     * 
     * @return
     */
    public final long getMaxValidityPeriod() {
        return this.maxValidityPeriod;
    }

    /**
     * 
     * @return
     */
    public synchronized final double getCurrentConfidence() {
        return this.currentConfidence;
    }

    /**
     * 
     * @return
     */
    public synchronized final ContextSource getCurrentContextSource() {
        return this.currentSource;
    }

    /*
     * Event support - context value changes *
     */
    /**
     * 
     * @param evt
     */
    protected synchronized void fireValueUnavailable(ContextValueEvent<T> evt) {
        for (ContextValueListener l : this.listeners) {
            l.valueUnavailable(evt);
        }
    }

    /**
     * 
     * @param evt
     */
    protected synchronized void fireValueRefreshed(ContextValueEvent<T> evt) {
        this.notifyAll();
        for (ContextValueListener l : this.listeners) {
            l.valueRefreshed(evt);
        }
    }

    /**
     * 
     * @param evt
     */
    protected synchronized void fireValueValid(ContextValueEvent<T> evt) {
        for (ContextValueListener l : this.listeners) {
            l.valueValid(evt);
        }
    }

    /**
     * 
     * @param evt
     */
    protected synchronized void fireValueObsolete(ContextValueEvent<T> evt) {
        for (ContextValueListener l : this.listeners) {
            l.valueObsolete(evt);
        }
    }

    /**
     * 
     * @param evt
     */
    protected synchronized void fireValueRequested(ContextValueEvent<T> evt) {
        for (ContextValueListener l : this.listeners) {
            l.valueRequested(evt);
        }
    }

    @Override
    public synchronized void addContextValueListener(ContextValueListener<T> l) {
        if (l == null) {
            throw new NullPointerException();
        }
        listeners.add(l);
    }

    @Override
    public synchronized void removeContextValueListener(ContextValueListener<T> l) {
        if (listeners.contains(l)) {
            listeners.remove(l);
        }
    }

    /**
     * 
     * @return
     */
    public synchronized long getCurrentTimestampRelative() {
        return (currentTimestamp - initializationTimestamp);
    }

    final synchronized void setCurrentSource(ContextSource newSource) {
        this.currentSource = newSource;
    }

    /**
     * 
     * @return
     */
    public synchronized final boolean isValid() {
        return this.status.equals(VALID);
    }

    /**
     * 
     * @return
     */
    public synchronized final boolean isObsolete() {
        return this.status.equals(OBSOLETE);
    }

    /**
     * 
     * @return
     */
    public synchronized final boolean isUnavailable() {
        return this.status.equals(UNAVAILABLE);
    }

    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        this.status = ContextValueStatus.UNAVAILABLE;
        this.initializationTimestamp = System.currentTimeMillis();
        //System.err.println("Max Refresh Period: " + maxRefreshPeriod);
        this.worker = new ContextObjectWorker(this);
        this.worker.activate();
    }
    
    @Override
    public String toString() {
        String result = "";
        String currentClass = (this.currentValue == null) ? "<no class>" : this.currentValue.getClass().getName();
        String currentValue = (this.currentValue == null) ? "<no value>" : this.currentValue.toString();
        String currentSource = (this.currentSource == null) ? "<no source>" : this.currentSource.toString();
        result = "ContextObject("+
                currentClass + ", " +
                currentValue + ", " + 
                currentSource + ", " +
                this.status +
                ")";
                
        return result;
    }
    
    public void deactivate() {
        worker.deactivate();
    }
    
    public void activate() {
        worker.activate();
    }

    public boolean isActive() {
        return worker.isActive();
    }
    
    /**
     * 
     */
    public static void deactivateAllInstances() {
        for (ContextObject o : INSTANCES) {
            if (o != null) o.deactivate();
        }
    }
}
